(*  Title:      Pure/more_unify.ML
    Author:     Makarius

Add-ons to Higher-Order Unification, outside the inference kernel.
*)

signature UNIFY =
sig
  include UNIFY
  val matchers: Context.generic -> (term * term) list -> Envir.env Seq.seq
  val matches_list: Context.generic -> term list -> term list -> bool
end;

structure Unify: UNIFY =
struct

(*Pattern matching*)
fun first_order_matchers thy pairs (Envir.Envir {maxidx, tenv, tyenv}) =
  let val (tyenv', tenv') = fold (Pattern.first_order_match thy) pairs (tyenv, tenv)
  in Seq.single (Envir.Envir {maxidx = maxidx, tenv = tenv', tyenv = tyenv'}) end
  handle Pattern.MATCH => Seq.empty;

(*General matching -- keep variables disjoint*)
fun matchers _ [] = Seq.single Envir.init
  | matchers context pairs =
      let
        val thy = Context.theory_of context;

        val maxidx = fold (Term.maxidx_term o #2) pairs ~1;
        val offset = maxidx + 1;
        val pairs' = map (apfst (Logic.incr_indexes ([], [], offset))) pairs;
        val maxidx' = fold (fn (t, u) => Term.maxidx_term t #> Term.maxidx_term u) pairs' ~1;

        val pat_tvars = fold (Term.add_tvars o #1) pairs' [];
        val pat_vars = fold (Term.add_vars o #1) pairs' [];

        val decr_indexesT =
          Term.map_atyps (fn T as TVar ((x, i), S) =>
            if i > maxidx then TVar ((x, i - offset), S) else T | T => T);
        val decr_indexes =
          Term.map_types decr_indexesT #>
          Term.map_aterms (fn t as Var ((x, i), T) =>
            if i > maxidx then Var ((x, i - offset), T) else t | t => t);

        fun norm_tvar env ((x, i), S) =
          let
            val tyenv = Envir.type_env env;
            val T' = Envir.norm_type tyenv (TVar ((x, i), S));
          in
            if (case T' of TVar (v, _) => v = (x, i) | _ => false) then NONE
            else SOME ((x, i - offset), (S, decr_indexesT T'))
          end;

        fun norm_var env ((x, i), T) =
          let
            val tyenv = Envir.type_env env;
            val T' = Envir.norm_type tyenv T;
            val t' = Envir.norm_term env (Var ((x, i), T'));
          in
            if (case t' of Var (v, _) => v = (x, i) | _ => false) then NONE
            else SOME ((x, i - offset), (decr_indexesT T', decr_indexes t'))
          end;

        fun result env =
          if Envir.above env maxidx then   (* FIXME proper handling of generated vars!? *)
            SOME (Envir.Envir {maxidx = maxidx,
              tyenv = Vartab.make (map_filter (norm_tvar env) pat_tvars),
              tenv = Vartab.make (map_filter (norm_var env) pat_vars)})
          else NONE;

        val empty = Envir.empty maxidx';
      in
        Seq.append
          (Seq.map_filter result (Unify.smash_unifiers context pairs' empty))
          (first_order_matchers thy pairs empty)
      end;

fun matches_list context ps os =
  length ps = length os andalso is_some (Seq.pull (matchers context (ps ~~ os)));


open Unify;

end;

